#include <QXmlAttributes>
#include <QXmlStreamWriter>
#include "RequestDescriptor.h"
#include "DataModel.h"
#include "DataObject.h"
#include "DataTable.h"
#include "scImpl.h"

#define FCT "DataTable_::DataTable_"
DataTable_::DataTable_(PkType tableId, const ColDescs * attributes) :
	parentPk(tableId), niceNameCol(0), dirty(false)
{
	//ctrc(Trc::WORK,APP,FCT)<<"pk "<<parentPk<<" attrs "<<attributes<<endl;
	if(attributes)
		attrs=*attributes;
	else
	{
		const DataObjects & attrTbl(DataModel::get()->getTable(EID_M_ATTRS).getPayload().dos);
		DOsCFk1Idx & fks(attrTbl.get<DataObject::Fk1Tag>());
		DOsCFk1Idx::const_iterator end(fks.upper_bound(parentPk));
		for(DOsCFk1Idx::const_iterator it(fks.lower_bound(parentPk)); it!=end; ++it)
		{
			const DataObject_ & pl(it->getPayload());
			//ctrc(Trc::LOOP,APP,FCT)<<"attr "<<it->getPk()<<" name "<<pl.at(M_ATTRS_NAME)<<" FK target "<<pl.at(M_ATTRS_TARGET).getAsInt()<<endl;
			//ctrc(Trc::LOOP,APP,FCT)<<"1 "<<pl.at(M_ATTRS_TNAME)<<endl;
			//ctrc(Trc::LOOP,APP,FCT)<<"2 "<<pl.at(M_ATTRS_NAME)<<endl;
			//ctrc(Trc::LOOP,APP,FCT)<<"3 "<<pl.at(M_ATTRS_FUNCTION)<<endl;
			//ctrc(Trc::LOOP,APP,FCT)<<"4 "<<pl.at(M_ATTRS_TARGET)<<endl;
			//ctrc(Trc::LOOP,APP,FCT)<<"5 "<<pl.at(M_ATTRS_DTYPE)<<endl;
			attrs.push_back(ColDesc(pl.at(M_ATTRS_TNAME).getQString(), pl.at(M_ATTRS_NAME).getQString(),
				pl.at(M_ATTRS_FUNCTION).getInt(), pl.at(M_ATTRS_TARGET).getDbKey(), pl.at(M_ATTRS_DTYPE).getInt()));
		}
	}
	for(unsigned int i(0); i<attrs.size(); ++i)
		if(attrs.at(i).techName=="name")
			niceNameCol=i;
}

#undef FCT
#define FCT "DataTable_::modifyCache"
void DataTable_::modifyCache(const Request * request, TimeType timestamp)
{
	if(request->stype==SCOPE_PK)
		return; // PKs are checked directly
	// update the cache history - no merging, timestamp updates are allowed for identical requests only
	// More general requests may not swallow/replace less general ones because then we could'nt touch
	// the special requests. This would lead to general request being "sticky" in the cache.
	bool updated(false);
	for(CacheTags::iterator it(cacheTags.begin()); it!=cacheTags.end(); ++it)
		if(it->rq==(*request))
		{
			it->tst=timestamp;
			updated=true;
			break;
		}
	if(!updated)
		cacheTags.push_back(CacheTag(request, timestamp));
}

#undef FCT
#define FCT "DataTable_::modifyData"
bool DataTable_::modifyData(const QXmlAttributes & attributes, DataUserRole userRole)
{
	PkType pk(attributes.value(QString(DB_COLT_PK)).toLongLong());
	if(pk<=0)
		throw new AppException(FCT ": no PK for DataObject", true);
	DOsPkIdx & dpk(getDos(userRole).get<DataObject::PkTag>());
	DOsPkIdx::const_iterator it(dpk.find(pk));
	if(it==dpk.end())
	{
		ctrc(Trc::WORK,APP,FCT)<<"have "<<getDos(userRole).size()<<" rows, creating row "<<pk<<" ("<<this<<")"<<endl;
		getDos(userRole).insert(DataObject(pk, attrs, attributes));
		dirty|=(userRole==DUR_CLI);
		return true;
	}
	else
	{
		//ctrc(Trc::WORK,APP,FCT)<<"updating row "<<endl;
		if(it->getPayload().modifyData(attributes))
		{ // check if any index-relevant attributes changed
			return makeConsistent(it);
		}
		// table itself is not dirty - see dirty flag of DataObject for details
		return false;
	}
}

#undef FCT
#define FCT "DataTable_::makeConsistent(ref)"
bool DataTable_::makeConsistent(const DataObject & dobj)
{
	DOsPkIdx & dpk(dos.get<DataObject::PkTag>());
	DOsPkIdx::const_iterator it(dpk.find(dobj.getPk()));
	if(it==dpk.end())
		return false;
	else
		return makeConsistent(it);
}

#undef FCT
#define FCT "DataTable_::makeConsistent(it)"
bool DataTable_::makeConsistent(DOsPkIdx::const_iterator doIt)
{
	DataObject_ & do_(doIt->getPayload());
	int fkc(0);
	DataObjectModifier dom;
	for(unsigned int i(0); i<attrs.size(); ++i)
	{
		switch(attrs.at(i).funct)
		{
		case AFUNCT_IK: // should never change actually...
			if(do_.at(i).getDbKey()!=doIt->getIk())
				dom.setIk(do_.at(i).getDbKey());
			break;
		case AFUNCT_IKS:
			if(do_.at(i).getAsInt()!=doIt->getIks())
				dom.setIks(do_.at(i).getAsInt());
			break;
		case AFUNCT_IKS2:
			if(do_.at(i).getAsInt()!=doIt->getIks2())
				dom.setIks_(do_.at(i).getAsInt());
			break;
		case AFUNCT_FPK:
		case AFUNCT_FIK:
			if(do_.at(i).getDbKey()!=doIt->getFkn(fkc))
				dom.setFk(fkc, do_.at(i).getDbKey());
			++fkc;
			break;
		default:
		// case AFUNCT_UNDEF: // don't care
		// case AFUNCT_PK: // identical due to select
		// case AFUNCT_DATA: // mostly harmless
		// case AFUNCT_LUT: // not indexed
			break;
		}
	}
	if(dom.isActive())
	{
		ctrc(Trc::WORK,APP,FCT)<<"updating "<<parentPk<<"|"<<doIt->getPk()<<" with index change "<<dom<<endl;
		dos.modify(doIt, dom);
		dirty=true;
		return true;
	}
	return false;
}

#undef FCT
#define FCT "DataTable_::toXml"
void DataTable_::toXml(QXmlStreamWriter * writer, PkType pk, DataUserRole userRole) const
{
	const DataObjects & ds(getDos(userRole));
	DOsPkIdx::const_iterator dit(ds.get<DataObject::PkTag>().find(pk));
	if(dit==ds.get<DataObject::PkTag>().end())
	{
		writer->writeEmptyElement(QString("entity"));
		writer->writeAttribute(QString("dead_PK"), QString::number(pk));
		return;
	}
	writer->writeStartElement(QString("entity"));
	dit->getPayload().toXml(writer);
	writer->writeEndElement();
}

#undef FCT
#define FCT "DataTable_::clearTouchedAll"
void DataTable_::clearTouchedAll()
{
	for(CacheTags::iterator it(cacheTags.begin()); it!=cacheTags.end(); ++it)
		it->clearTouched();
	for(DataObjects::iterator it(dos.begin()); it!=dos.end(); ++it)
		it->getPayload().clearTouched();
}

#undef FCT
#define FCT "DataTable_::setTouched"
void DataTable_::setTouched(const Request & request)
{
	// ctrc(Trc::LOOP,APP,FCT)<<"try to touch "<<request<<endl;
	for(CacheTags::iterator it(cacheTags.begin()); it!=cacheTags.end(); ++it)
	{
		if(it->rq==request)
		{
			it->setTouched();
			// ctrc(Trc::LOOP,APP,FCT)<<"done!"<<endl;
			break;
		}
	}
}

#undef FCT
#define FCT "DataTable_::cleanGarbage"
void DataTable_::cleanGarbage()
{
	ctrc(Trc::LOOP,APP,FCT)<<"entity "<<parentPk<<endl;
	if(parentPk==EID_M_ENTITIES || parentPk==EID_M_ATTRS)
	{ // never scrap anything in the meta tables and don't care for requests
		cacheTags.clear();
		return;
	}
	for(CacheTags::iterator it(cacheTags.begin()); it!=cacheTags.end(); ) // no increment!
	{
		if(it->isTouched())
			++it;
		else
		{
			ctrc(Trc::LOOP,APP,FCT)<<"remove CacheTag "<<it->rq<<endl;
			it=cacheTags.erase(it);
		}
	}
	for(DataObjects::iterator it(dos.begin()); it!=dos.end(); ) // no increment!
	{
		if(it->getPayload().isTouched())
			++it;
		else
		{
			ctrc(Trc::LOOP,APP,FCT)<<"remove DataObject "<<it->getPk()<<endl;
			it=dos.erase(it);
		}
	}
	//ctrc(Trc::LOOP,APP,FCT)<<"done"<<endl;
}

#undef FCT
#define FCT "DataTable::DataTable"
DataTable::DataTable(PkType tableId, const char * techName, QString name, const char * techRowName, QString rowName, DataDomain domain, const ColDescs * attributes) :
	pk(tableId),
	techName(techName), techRowName(techRowName),
	techNameQ(techName), techRowNameQ(techRowName), name(name), rowName(rowName),
	domain(domain),
	pl(tableId, attributes)
{
	ctrc(Trc::WORK,APP,FCT)<<"pk "<<pk<<" domain "<<domain<<" this "<<this<<endl;
}

#undef FCT
#define FCT "DataTable::~DataTable"
DataTable::~DataTable()
{
}

#undef FCT
#define FCT "DataTable::scopeCovered"
bool DataTable::scopeCovered(const Request * requ) const
{
	if(requ->entity!=pk)
	{
		ctrc(Trc::WORK,APP,FCT)<<"table "<<pk<<" can't cover request for "<<requ->entity<<" HOW SHOULD I?"<<endl;
		return false;
	}
	if(requ->stype==SCOPE_UNDEF || requ->rtype!=REQU_READ)
	{
		ctrc(Trc::WORK,APP,FCT)<<"a SCOPE_UNDEF or !REQU_READ can never be covered ("<<pk<<")"<<endl;
		return false;
	}
	for(CacheTags::const_iterator it(pl.cacheTags.begin()); it!=pl.cacheTags.end(); ++it)
	{
		// TODO performance - cache hit test may recognize coverage by other stype's - how to deal during touch?
		if(it->rq==(*requ))
		{
			//ctrc(Trc::LOOP,APP,FCT)<<"selectAllTime "<<pl.selectAllTime<<endl;
			return true;
		}
	}
	/*
	case SCOPE_IKTS:
	{
		DOsCIkIdx & iks(pl.dos.get<DataObject::IkTag>());
		for(Pks::const_iterator it(requ->pks.begin()); it!=requ->pks.end(); ++it)
		{
			bool found(false);
			DOsCIkIdx::const_iterator end(iks.upper_bound(*it));
			for(DOsCIkIdx::const_iterator iit(iks.lower_bound(*it)); iit!=end; ++iit)
			{
				if(iit->getIks()<=requ->deadline && iit->getIks2()>requ->deadline)
				{
					found=true;
					break;
				}
			}
			if(!found)
				return false;
		}
		return true;
	}
	case SCOPE_PK:
	{
		if(pl.selectAllTime!=0)
			return true;
		for(Pks::const_iterator it(requ->pks.begin()); it!=requ->pks.end(); ++it)
			if(pl.dos.find(*it)==pl.dos.end())
				return false;
		return true;
	}
	*/
	return false;
}

#undef FCT
#define FCT "DataTable::getByIk"
const DataObject * DataTable::getByIk(PkType ik, TimeType deadline) const
{
	DOsCIkIdx & iks(pl.dos.get<DataObject::IkTag>());
	DOsCIkIdx::const_iterator end(iks.upper_bound(ik));
	for(DOsCIkIdx::const_iterator it(iks.lower_bound(ik)); it!=end; ++it)
	{
		if(it->getIks()<=deadline && it->getIks2()>deadline)
			return &(*it);
	}
	ctrc(Trc::WORK,APP,FCT)<<"incomplete timeline in entity "<<pk<<" for ik "<<ik<<" and deadline "<<deadline<<endl;
	return 0;
	//ostringstream os;
	//os <<FCT<<": incomplete timeline in entity "<<pk<<" for ik "<<ik<<" and deadline "<<deadline;
	//throw new AppException(os.str().c_str(), false);
}

#undef FCT
#define FCT "DataTable::getByPk"
const DataObject * DataTable::getByPk(PkType doPk, DataUserRole userRole) const
{
	DOsCPkIdx & pks(pl.getDos(userRole).get<DataObject::PkTag>());
	DOsCPkIdx::const_iterator it(pks.find(doPk));
	if(it!=pks.end())
		return &(*it);
	ctrc(Trc::WORK,APP,FCT)<<"invalid pk "<<doPk<<" for entity "<<pk<<" (role "<<userRole<<")"<<endl;
	return 0;
	//ostringstream os;
	//os <<FCT<<": invalid pk "<<doPk<<" for entity "<<pk;
	//throw new AppException(os.str().c_str(), false);
}

#undef FCT
#define FCT "DataTable::getAttrList"
QString DataTable::getAttrList() const
{
	QString res;
	bool follow(false);
	for(ColDescs::const_iterator it(pl.attrs.begin()); it!=pl.attrs.end(); ++it)
	{
		if(follow)
			res+=",";
		else
			follow=true;
		res+=it->techName;
	}
	return res;
}

#undef FCT
#define FCT "DataTable::getAttrValueList"
QString DataTable::getAttrValueList(const PkType pk) const
{
	QString res;
	const DataObject * dobj(getByPk(pk));
	if(!dobj)
		return res;
	bool follow(false);
	ColDescs::const_iterator it(pl.attrs.begin());
	int col(1);
	for(++it/* skip PK */; it!=pl.attrs.end(); ++it, ++col)
	{
		const DataCell dc(dobj->getPayload().at(col));
		if(follow)
			res+=",";
		else
			follow=true;
		res+=it->techName+"=";
		switch(pl.attrs.at(col).dtype)
		{
		case ADTYPE_I32S:
			res+=QString::number(dc.getInt());
			break;
		case ADTYPE_DBKEY:
			res+=QString::number(dc.getDbKey());
			break;
		case ADTYPE_DATIM:
			res+=QString::number(dc.getAsInt());
			break;
		case ADTYPE_F64:
			res+=QString::number(dc.getDouble());
			break;
		default:
			res+=QString("\"")+dc.getAsQString(false)+"\"";
			break;
		}
	}
	return res;
}
